using System;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace WindowFinder
{
    using WindowKey = IntPtr;
    using System.Diagnostics;
    using System.Drawing;

    /// <summary>
    /// Object used to control a Windows Form.
    /// </summary>
    public class Window : IEquatable<Window>
    {
        /// <summary>
        /// Win32 API Imports
        /// </summary>
        [DllImport("user32.dll")]
        private static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
        [DllImport("user32.dll")]
        private static extern bool SetForegroundWindow(IntPtr hWnd);
        [DllImport("user32.dll")]
        private static extern bool IsIconic(IntPtr hWnd);
        [DllImport("user32.dll")]
        private static extern bool IsZoomed(IntPtr hWnd);
        [DllImport("user32.dll")]
        private static extern IntPtr GetForegroundWindow();
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, out IntPtr ProcessId);
        [DllImport("user32.dll")]
        private static extern IntPtr AttachThreadInput(IntPtr idAttach, IntPtr idAttachTo, int fAttach);
        [DllImport("user32.dll")]
        private static extern int GetWindowText(IntPtr hWnd, StringBuilder title, int size);
        [DllImport("user32.dll")]
        private static extern int GetWindowModuleFileName(IntPtr hWnd, StringBuilder title, int size);
        [DllImport("user32.dll")]
        private static extern bool IsWindowVisible(IntPtr hWnd);
        [DllImport("user32.dll")]
        private static extern bool IsWindow(IntPtr hWND);

        [DllImport("Kernel32.dll")]
        private static extern IntPtr GetCurrentThreadId();

        /// <summary>
        /// Win32 API Constants for ShowWindowAsync()
        /// </summary>
        private const int SW_HIDE = 0;
        private const int SW_SHOW = 5;
        private const int SW_SHOWNORMAL = 1;
        private const int SW_SHOWMINIMIZED = 2;
        private const int SW_SHOWMAXIMIZED = 3;
        private const int SW_SHOWNOACTIVATE = 4;
        private const int SW_RESTORE = 9;
        private const int SW_SHOWDEFAULT = 10;

        /// <summary>
        /// Private Fields
        /// </summary>
        private readonly WindowKey m_hWnd;
        private String m_title;
        private String m_processName;
        private String m_processFile;
        private Icon m_processIcon;

        /// <summary>
        /// Window Object's Public Properties
        /// </summary>
        public WindowKey Key
        {
            get { return m_hWnd; }
        }

        public bool IsValid
        {
            get
            {
                return IsWindow(m_hWnd);
            }
        }

        public String Title
        {
            get
            {
                if (m_title == null)
                {
                    StringBuilder title = new StringBuilder(256);
                    GetWindowText(m_hWnd, title, 256);
                    m_title = title.ToString();
                }
                return m_title;
            }
        }

        public String ProcessName
        {
            get
            {
                if (m_processName == null)
                {
                    loadProcessInfo();
                }
                return m_processName;
            }
        }

        public String ProcessFile
        {
            get
            {
                if (m_processFile == null)
                {
                    loadProcessInfo();
                }
                return m_processFile;
            }
        }

        public Icon ProcessIcon
        {
            get
            {
                if (m_processIcon == null)
                {
                    loadProcessInfo();
                }
                return m_processIcon;
            }
        }

        private void loadProcessInfo()
        {
            Process proc = getProcess();
            if (proc != null)
            {
                using (proc)
                {
                    m_processName = proc.MainModule.ModuleName;
                    m_processFile = proc.MainModule.FileName;
                }
                m_processIcon = Icon.ExtractAssociatedIcon(m_processFile);
            }
        }

        private Process getProcess()
        {
            IntPtr procId;
            if (GetWindowThreadProcessId(m_hWnd, out procId) != IntPtr.Zero)
            {
                try
                {
                    return Process.GetProcessById((int)procId);
                }
                catch (Exception)
                {
                   // ignore
                }

            }
            return null;
        }

        /// <summary>
        /// Sets this Window Object's visibility
        /// </summary>
        public bool Visible
        {
            get { return IsWindowVisible(m_hWnd); }
            set
            {
                //show the window
                if (value == true)
                {
                    ShowWindowAsync(m_hWnd, SW_SHOWNORMAL);
                }
                //hide the window
                else
                {
                    ShowWindowAsync(m_hWnd, SW_HIDE);
                }
            }
        }

        /// <summary>
        /// Constructs a Window Object
        /// </summary>
        /// <param name="Title">Title Caption</param>
        /// <param name="hWnd">Handle</param>
        /// <param name="Process">Owning Process</param>
        public Window(WindowKey hWnd)
        {
            m_hWnd = hWnd;
        }

        public void Update()
        {
            // set to null so they will be updated
            m_title = null;
            m_processName = null;
        }

        //Override ToString() 
        public override string ToString()
        {
            //return the title if it has one, if not return the process name
            if (Title.Length > 0)
            {
                return Title;
            }
            else
            {
                return ProcessName;
            }
        }

        /// <summary>
        /// Sets focus to this Window Object
        /// </summary>
        public void Activate()
        {
            if (IsIconic(m_hWnd))
            {
                ShowWindowAsync(m_hWnd, SW_RESTORE);
            }
            else
            {
                //ShowWindowAsync(m_hWnd, SW_SHOWNORMAL);
            }

            if (m_hWnd == GetForegroundWindow())
                return;

            //IntPtr ThreadID1 = GetWindowThreadProcessId(GetForegroundWindow(),IntPtr.Zero);
            IntPtr ThreadID1 = GetCurrentThreadId();
            IntPtr ProcessID2;
            IntPtr ThreadID2 = GetWindowThreadProcessId(m_hWnd, out ProcessID2);

            if (ThreadID1 != ThreadID2)
            {
                AttachThreadInput(ThreadID1, ThreadID2, 1);
                SetForegroundWindow(m_hWnd);
                AttachThreadInput(ThreadID1, ThreadID2, 0);
            }
            else
            {
                SetForegroundWindow(m_hWnd);
            }
        }

        public bool Equals(Window other)
        {
            return m_hWnd == other.m_hWnd;
        }

    }

    /// <summary>
    /// Collection used to enumerate Window Objects
    /// </summary>
    public class WindowEnumerator
    {
        /// <summary>
        /// Win32 API Imports
        /// </summary>

        [DllImport("user32.dll")]
        private static extern int EnumWindows(EnumWindowsProc ewp, int lParam);

        //delegate used for EnumWindows() callback function
        private delegate bool EnumWindowsProc(int hWnd, int lParam);

        private List<Window> m_windows;

        //Declare a callback delegate for EnumWindows() API call
        private readonly EnumWindowsProc ewp;

        //Object's private fields
        private bool m_invisible = false;
        private bool m_notitle = false;

        /// <summary>
        /// Collection Constructor with additional options
        /// </summary>
        /// <param name="Invisible">Include invisible Windows</param>
        /// <param name="Untitled">Include untitled Windows</param>
        public WindowEnumerator(bool Invisible = false, bool Untitled = false)
        {
            m_invisible = Invisible;
            m_notitle = Untitled;

            ewp = new EnumWindowsProc(this.EvalWindow);
        }

        public List<Window> FindAll()
        {
            m_windows = new List<Window>();
            //Enumerate all Windows
            EnumWindows(ewp, 0);
            return m_windows;
        }

        //EnumWindows CALLBACK function
        private bool EvalWindow(int hWnd, int lParam)
        {
            WindowKey key = (IntPtr)hWnd;

            Window wnd = new Window(key);

            if (m_invisible == false && !wnd.Visible)
                return (true);

            if (m_notitle == false && wnd.Title.Length == 0)
                return (true);

            m_windows.Add(wnd);

            return (true);
        }

    //private static bool KeepWindowHandleInAltTabList(IntPtr window)
    //{
    //    if (window == Win32.GetShellWindow())   //Desktop
    //        return false;

    //    //http://stackoverflow.com/questions/210504/enumerate-windows-like-alt-tab-does
    //    //http://blogs.msdn.com/oldnewthing/archive/2007/10/08/5351207.aspx
    //    //1. For each visible window, walk up its owner chain until you find the root owner. 
    //    //2. Then walk back down the visible last active popup chain until you find a visible window.
    //    //3. If you're back to where you're started, (look for exceptions) then put the window in the Alt+Tab list.
    //    IntPtr root = Win32.GetAncestor(window, Win32.GaFlags.GA_ROOTOWNER);

    //    if (GetLastVisibleActivePopUpOfWindow(root) == window)
    //    {
    //        WindowInformation wi = new WindowInformation(window);

    //        if (wi.className == "Shell_TrayWnd" ||                          //Windows taskbar
    //            wi.className == "DV2ControlHost" ||                         //Windows startmenu, if open
    //            (wi.className == "Button" && wi.windowText == "Start") ||   //Windows startmenu-button.
    //            wi.className == "MsgrIMEWindowClass" ||                     //Live messenger's notifybox i think
    //            wi.className == "SysShadow" ||                              //Live messenger's shadow-hack
    //            wi.className.StartsWith("WMP9MediaBarFlyout"))              //WMP's "now playing" taskbar-toolbar
    //            return false;

    //        return true;
    //    }
    //    return false;
    //}

    //private static IntPtr GetLastVisibleActivePopUpOfWindow(IntPtr window)
    //{
    //    IntPtr lastPopUp = Win32.GetLastActivePopup(window);
    //    if (Win32.IsWindowVisible(lastPopUp))
    //        return lastPopUp;
    //    else if (lastPopUp == window)
    //        return IntPtr.Zero;
    //    else
    //        return GetLastVisibleActivePopUpOfWindow(lastPopUp);
    //}

    }

    public class WindowList
    {
        private String m_titleFilter;
        public String TitleFilter
        {
            get
            {
                return m_titleFilter;
            }
            set
            {
                if ( value != m_titleFilter )
                {
                    if (value != null && m_titleFilter != null &&
                        value.StartsWith(m_titleFilter))
                    {
                        m_titleFilter = value;
                        // apply a more restrictive filter
                        applyFilter(true);
                    }
                    else
                    {
                        m_titleFilter = value;
                        // apply a new filter
                        applyFilter(false);
                    }
                }
            }
        }

        private readonly int m_historySize = 5;
        private readonly List<Window> m_history = new List<Window>();
        private List<Window> m_filteredHistory;
        public IEnumerable<Window> History
        {
            get
            {
                return m_filteredHistory;
            }
        }

        private List<Window> m_filteredWindows;
        public IEnumerable<Window> Windows
        {
            get
            {
                return m_filteredWindows;
            }
        }

        public WindowList()
        {
        }

        public void Update()
        {
            // Remove all invalid windows from hist
            m_history.RemoveAll(new Predicate<Window>(delegate(Window w){return !w.IsValid;}));
            if (m_history.Count > m_historySize)
            {
                m_history.RemoveRange(m_historySize - 1, m_history.Count - m_historySize);
            }
            applyFilter(false);
        }

        private void applyFilter(bool refine)
        {
            List<Window> src;
            if (refine && m_filteredWindows != null)
            {
                src = m_filteredWindows;
            }
            else
            {
                src = new WindowEnumerator(false, false).FindAll();
            }

            m_filteredHistory = m_history.FindAll(new Predicate<Window>(windowMatches));
            m_filteredWindows = src.FindAll(new Predicate<Window>(windowMatches));
            // remove all history
            m_filteredWindows.RemoveAll(new Predicate<Window>(delegate(Window w) { return m_history.Contains(w); }));
            
        }

        private bool windowMatches(Window w)
        {
            return m_titleFilter == null || w.Title.ToLower().Contains(m_titleFilter.ToLower());
        }

        public void showAll(IEnumerable<Window> selection)
        {
            foreach (Window wnd in selection)
            {
                show(wnd);
            }
        }

        public void show(Window wnd)
        {
            wnd.Activate();
            m_history.Add(wnd);
        }
    }
}
